#include "MeshDraw.hpp"
#ifndef NO_MESH_DRAW
#include "GLee.h"
#include "gl.hpp"
#include <set>
#include <algorithm>

//const float MeshDraw::sMaterialColors[18][3] = 
//{ 
//   {79, 184, 196}, {255,255,0}, {0, 255, 0}, {0, 255, 255}, {0, 0, 255}, {255, 0, 255}, 
//   {128, 255, 0}, {255, 128, 0}, {0, 128, 255}, {128, 0, 255}, {255, 128, 128}, {128, 128, 255}, 
//   {243, 117, 76}, {127, 145, 110}, {128, 23, 82},  {249, 249, 19}, {102,2, 102}, {240, 240, 240}
//};

const float MeshDraw::sMaterialColors[18][3] = 
{  
   {255, 0, 0 }, {204, 255, 204 }, {255, 0, 255 }, {255, 204, 255 }, {255, 153, 0 }, {102, 102, 0 },
   {255, 255, 0 }, {0, 0, 255 }, { 0, 102, 0 }, {0, 0, 0 }, {255, 204, 204 }, {204, 204, 255 },
{102, 0, 102 }, {0, 255, 255 }, {0, 255, 0 }, {204, 204, 204 }, {102, 102, 102 }, {255, 255, 255 }
};


MeshDraw::MeshDraw()
{
   mUseMaterials = true;
   mVBO_Buffers[0] = 0;
   mVBO_Buffers[1] = 0;
}

void MeshDraw::BindVertexBuffer()
{
   glEnableClientState(GL_NORMAL_ARRAY);
   glEnableClientState(GL_VERTEX_ARRAY);
   if (GLEE_ARB_vertex_buffer_object)
   {
      if (mVBO_Buffers[0] != 0)
      {
         glBindBufferARB(GL_ARRAY_BUFFER_ARB, mVBO_Buffers[0]);
         char* nullPtr(0);
         glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), nullPtr);
         glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullPtr + sizeof(Vec3));
      }
   }
   else
   {
      glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), &mVertices[0].p);
      glNormalPointer(GL_FLOAT, 6 * sizeof(float), &mVertices[0].n);
   }
}
void MeshDraw::BindIndexBuffer()
{
   if (GLEE_ARB_vertex_buffer_object)
   {
      glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, mVBO_Buffers[1]); 
   }
}
void MeshDraw::ReleaseVertexBuffer()
{
   glDisableClientState(GL_NORMAL_ARRAY);
   glDisableClientState(GL_VERTEX_ARRAY);
   if (GLEE_ARB_vertex_buffer_object)
   {
      glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
   }
}

void MeshDraw::ReleaseIndexBuffer()
{
   if (GLEE_ARB_vertex_buffer_object)
   {
      glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); 
   }
}

void MeshDraw::PrepareBuffers()
{
   glEnable(GL_DEPTH_TEST);
   BindVertexBuffer();
   BindIndexBuffer();
   gl::AssertGlError();
}

void MeshDraw::DrawSection(int aSection, float aAlpha)
{
   PrepareBuffers();
   DrawSectionP(aSection, aAlpha);
   UnbindVBO();
}

void MeshDraw::DrawSectionP(int aSection, float aAlpha)
{
   MaterialSection& section = mSections[aSection];
   if (mUseMaterials)
   {
      Color dim = section.mColor * .85f;
      Color c = section.mColor;
      c.a = aAlpha;
      dim.a = aAlpha;
      glMaterialfv(GL_FRONT, GL_DIFFUSE, &c.r);
      glMaterialfv(GL_FRONT, GL_AMBIENT, &dim.r);
      glColor3fv(&section.mColor.r);
   }
   if (GLEE_ARB_vertex_buffer_object)
   {
      if (mVBO_Buffers[0] != 0)
      {
         gl::AssertGlError();
         int* nullPtr(0);
         glDrawElements(GL_TRIANGLES, section.mFaceCount * 3, GL_UNSIGNED_INT, nullPtr + section.mOffset);
         gl::AssertGlError();
      }
   }
   else
   {
      glDrawElements(GL_TRIANGLES, section.mFaceCount * 3, GL_UNSIGNED_INT, &mIndices[section.mOffset]);
   }
}
void MeshDraw::Draw(float aAlpha)
{
   if (mSections.empty())
   {
      return;
   }
   //glDisable(GL_LIGHTING);
   PrepareBuffers();
   for (size_t i = 0; i < mSections.size(); ++i)
   {
      DrawSectionP(i, aAlpha);
   }
   UnbindVBO();
   //glEnable(GL_LIGHTING);
}

struct FaceData
{
   bool operator<(const FaceData& aLHS) const { return material < aLHS.material; }
   int vertex[3];
   int material;
   bool mIsBackFace;
};

Color MeshDraw::GetMaterialColor(int aMaterial)
{
   //aMaterial--;
   if (aMaterial >= 0 && aMaterial < 18)
   {
      const float* c = sMaterialColors[aMaterial];
      return Color(c[0]/255.0f, c[1]/255.0f, c[2]/255.0f);
   }
   return Color(.3f, .3f, .3f);

   
}
void MeshDraw::LoadVBO()
{
   if (mVertices.empty()) return;
   if (GLEE_ARB_vertex_buffer_object)
   {
      bool empty = (mVertices.empty() || mIndices.empty());
      if (!empty && mVBO_Buffers[0] == 0)
      {
         glGenBuffersARB(2, mVBO_Buffers);
      }
      else if (empty && mVBO_Buffers[0] != 0)
      {
         glDeleteBuffersARB(2, mVBO_Buffers);
         mVBO_Buffers[0] = mVBO_Buffers[1] = 0;
         return;
      }
      else if (empty)
      {
         return;
      }
      glBindBufferARB(GL_ARRAY_BUFFER_ARB, mVBO_Buffers[0]);
      glBufferDataARB(GL_ARRAY_BUFFER_ARB, mVertices.size() * sizeof(Vertex), &mVertices[0], GL_STATIC_DRAW);
      gl::AssertGlError();
      glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, mVBO_Buffers[1]);
      glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, mIndices.size() * sizeof(int), &mIndices[0], GL_STATIC_DRAW);
      gl::AssertGlError();
   }
}

void MeshDraw::SetMesh(const Mesh& aMesh)
{
   mMeshPtr = (Mesh*)&aMesh;
   mVertices.clear();
   mIndices.clear();
   mSections.clear();

   
   int vertexCount = aMesh.verts.size();
   std::vector<bool> usedVertices(vertexCount, false);
   mVertices.resize(aMesh.verts.size());
   for (size_t i = 0; i < aMesh.verts.size(); ++i)
   {
      mVertices[i].p = aMesh.verts[i];
   }
   //mVertices.insert(mVertices.end(), mVertices.begin(), mVertices.end());

   std::vector<FaceData> faces(aMesh.faces.size() * 2);
   for (size_t i = 0; i < aMesh.faces.size(); ++i)
   {
      const Face& f = aMesh.faces[i];
      FaceData& fd1 = faces[i*2];
      FaceData& fd2 = faces[i*2+1];
      fd1.vertex[0] = f.vertex[0];
      fd1.vertex[1] = f.vertex[1];
      fd1.vertex[2] = f.vertex[2];
      fd1.material = f.materials[0];
      fd1.mIsBackFace = false;

      fd2.vertex[2] = f.vertex[0];
      fd2.vertex[1] = f.vertex[1];
      fd2.vertex[0] = f.vertex[2];
      fd2.material = f.materials[1];
      fd2.mIsBackFace = true;
   }
   std::sort(faces.begin(), faces.end());

   int currentMaterial = -2;
   //std::set<int> usedVertices;
   int lastOffset = 0;
   for (size_t i = 0; i < faces.size(); ++i)
   {
      FaceData& f = faces[i];

      if (currentMaterial != f.material)
      {
         MaterialSection section;
         section.mFaceCount = (mIndices.size() - lastOffset) / 3;
         if (section.mFaceCount > 0)
         {
            section.mMaterialIndex = currentMaterial;
            section.mOffset = lastOffset;
            lastOffset = mIndices.size();
            section.mColor = GetMaterialColor(currentMaterial);
            mSections.push_back(section);
         }
         currentMaterial = f.material;
      }
      int keyVertex = -1;
      int nextVertex = 1;
      if (! usedVertices[f.vertex[0]])
      {
         keyVertex = 0;
         nextVertex = 1;
      }
      else if (! usedVertices[f.vertex[1]])
      {
         keyVertex = 1;
         nextVertex = 2;
      }
      else if (! usedVertices[f.vertex[2]])
      {
         keyVertex = 2;
         nextVertex = 0;
      }

      if (keyVertex != -1)
      {
         keyVertex = f.vertex[keyVertex];
         usedVertices[keyVertex] = true;
         //usedVertices.insert(f.vertex[keyVertex]);
      }
      else
      {
         keyVertex = mVertices.size();
         mVertices.push_back(mVertices[f.vertex[0]]);
         nextVertex = 1;
      }

      Vec3& v1 = mVertices[f.vertex[0]].p;
      Vec3& v2 = mVertices[f.vertex[1]].p;
      Vec3& v3 = mVertices[f.vertex[2]].p;
      Vec3 norm = cross((v2 - v1), (v3 - v2)).normal();
      int vi1 = keyVertex, vi2 = f.vertex[nextVertex], vi3 = f.vertex[(nextVertex + 1)%3];
      mVertices[vi1].n = -norm;
      
      mIndices.push_back(vi2);
      mIndices.push_back(vi3);
      mIndices.push_back(vi1);

      //mVertices[f.vertex[0]].n += norm;
      //mVertices[f.vertex[1]].n += norm;
      //mVertices[f.vertex[2]].n += norm;
      //if (! f.mIsBackFace)
      //{
         //mVertices[keyVertex].n = cross((v2 - v1), (v3 - v2)).normal();
      //}
      //mIndices.push_back(keyVertex);
      //mIndices.push_back(f.vertex[(nextVertex+2)%3]);
      //mIndices.push_back(f.vertex[nextVertex]);
      //mIndices.push_back(f.vertex[(nextVertex+1)%3]);
   }

   {
      MaterialSection section;
      section.mFaceCount = (mIndices.size() - lastOffset) / 3;
      if (section.mFaceCount > 0)
      {
         section.mMaterialIndex = currentMaterial;
         section.mOffset = lastOffset;
         lastOffset = mIndices.size();
         section.mColor = GetMaterialColor(currentMaterial);
         mSections.push_back(section);
      }
   }


   //for (size_t i = 0; i < mVertices.size(); ++i)
   //{
   //   mVertices[i].n.normalize();
   //}

   LoadVBO();
}

void MeshDraw::UnbindVBO()
{
   ReleaseVertexBuffer();
   ReleaseIndexBuffer();
}


#endif